package hn.sigit.logic.ladmshadow;

import hn.sigit.model.hnd.cadastre.HND_Parcel;
import hn.sigit.model.hnd.cadastre.HND_Property;
import hn.sigit.dao.hnd.cadastre.HND_ParcelDAO;
import hn.sigit.dao.hnd.cadastre.HND_PropertyDAO;
import hn.sigit.model.hnd.ladmshadow.BAUnit;
import hn.sigit.model.hnd.ladmshadow.LAPoint;
import hn.sigit.model.hnd.ladmshadow.Parcel;
import hn.sigit.model.hnd.ladmshadow.Party;
import hn.sigit.model.hnd.ladmshadow.Property;
import hn.sigit.model.hnd.ladmshadow.RRR;
import hn.sigit.model.hnd.ladmshadow.Responsibility;
import hn.sigit.model.hnd.ladmshadow.Restriction;
import hn.sigit.model.hnd.ladmshadow.Right;
import hn.sigit.model.hnd.ladmshadow.SpatialUnit;
import hn.sigit.dao.hnd.ladmshadow.ParcelDAO;
import hn.sigit.dao.hnd.ladmshadow.PartyDAO;
import hn.sigit.dao.hnd.ladmshadow.PropertyDAO;
import hn.sigit.dao.hnd.ladmshadow.RRRDAO;
import hn.sigit.dao.hnd.ladmshadow.SpatialUnitDAO;
import hn.sigit.dao.ladm.administrative.LA_BAUnitDAO;
import hn.sigit.model.ladm.administrative.LA_BAUnit;
import hn.sigit.model.ladm.administrative.LA_RRR;
import hn.sigit.model.ladm.administrative.LA_Responsibility;
import hn.sigit.model.ladm.administrative.LA_Restriction;
import hn.sigit.model.ladm.administrative.LA_Right;
import hn.sigit.model.ladm.party.LA_Party;
import hn.sigit.model.ladm.spatialunit.LA_SpatialUnit;
import hn.sigit.model.ladm.spatialunit.surveyingandrepresentation.LA_Point;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.Persistence;

public class Util {
	public static void copyMemberValues(Object src, Object dest, boolean onlyWithColumnAnnotation) {
		Method[] targetMethods = dest.getClass().getMethods();
		
		String methodName;
		try	{
			for (Method m : targetMethods) {
				methodName = m.getName();
				if (/*m.getAnnotation(javax.persistence.Column.class) != null
						&&*/ methodName.startsWith("set")) {
					
					Method getterMethod;
					try {
						getterMethod = src.getClass().getMethod("get" + methodName.substring(3), (Class[])null);
						
						if (getterMethod.getAnnotation(javax.persistence.Id.class) != null)
							getterMethod = null;
						else if (onlyWithColumnAnnotation && getterMethod.getAnnotation(javax.persistence.Column.class) == null
								&& getterMethod.getAnnotation(org.hibernate.annotations.Columns.class) == null
								&& getterMethod.getAnnotation(javax.persistence.OneToOne.class) == null)
							getterMethod = null;
					}
					catch (NoSuchMethodException nse) {
						getterMethod = null;
					}
					
					if (getterMethod != null) {
						Class getterReturnType = getterMethod.getReturnType();
						Class[] setterParams = m.getParameterTypes();
						
						if (setterParams != null && setterParams.length == 1
								&& setterParams[0] == getterReturnType) {
							
							Object res = getterMethod.invoke(src, new Object[] {});
							m.invoke(dest, res);
						}
					}
				}
			}
		}
		catch (InvocationTargetException ite) {
			System.err.println(ite.getLocalizedMessage());
		}
		catch (IllegalAccessException iae) {
			System.err.println(iae.getLocalizedMessage());
		}
	}
	
	public static Party LA_PartyToParty(LA_Party laParty, long presentationId, boolean isReadOnly, boolean isSnapshot) {
		if (laParty == null) return null;
		
		Party party = new Party();
		copyMemberValues(laParty, party, true);
		party.setLadmId(laParty.getpID());
		party.setPresentationId(presentationId);
		party.setExtParty(laParty.getExtParty());
		
		return party;
	}
	
	public static RRR LA_RRRToRRR(LA_RRR laRrr, long presentationId, boolean isReadOnly, boolean isSnapshot) {
		if (laRrr == null) return null;
		
		RRR rrr = null;
		
		if (laRrr instanceof LA_Responsibility)
			rrr = Responsibility.newResponsibility(laRrr.getrID(), presentationId, false, isSnapshot);
		else if (laRrr instanceof LA_Right)
			rrr = Right.newRight(laRrr.getrID(), presentationId, false, isSnapshot);
		else if (laRrr instanceof LA_Restriction)
			rrr = Restriction.newRestriction(laRrr.getrID(), presentationId, false, isSnapshot);

		if (rrr != null)
			copyMemberValues(laRrr, rrr, true);
		
		return rrr;
	}
	public static LA_RRR RRRToLA_RRR(RRR rrr, LA_BAUnit laBAUnit) {
		if (rrr == null) return null;
		
		LA_RRR laRrr = null;

		laRrr = null;
		if (rrr instanceof Right)
			laRrr = new LA_Right();
		else if (rrr instanceof Restriction)
			laRrr = new LA_Restriction();
		else if (rrr instanceof Responsibility)
			laRrr = new LA_Responsibility();
		
		if (laRrr != null) {
			copyMemberValues(rrr, laRrr, true);
			laRrr.setBaunit(laBAUnit);
		}
		
		return laRrr;
	}
	
	public static Property HND_PropertyToPropery(HND_Property hndProperty, long presentationId, boolean isReadOnly, boolean isSnapshot) {
		Set<LA_SpatialUnit> laSpatialUnitSet = hndProperty.getSpatialUnits();
		
		return HND_PropertyToPropery(hndProperty, laSpatialUnitSet, presentationId, isReadOnly, isSnapshot);
	}
	
	public static Property HND_PropertyToPropery(HND_Property hndProperty, HND_Parcel hndParcel, long presentationId, boolean isReadOnly, boolean isSnapshot) {
		Set<LA_SpatialUnit> laSpatialUnitSet = new HashSet<LA_SpatialUnit>();
		laSpatialUnitSet.add(hndParcel);
		
		return HND_PropertyToPropery(hndProperty, laSpatialUnitSet, presentationId, isReadOnly, isSnapshot); 
	}
	
	public static Property HND_PropertyToPropery(HND_Property hndProperty, Set<LA_SpatialUnit> laSpatialUnitSet, long presentationId, boolean isReadOnly, boolean isSnapshot) {
		//TODO: Validar cuando no se encuentra
		Property property = Property.newProperty(hndProperty.getuID(), presentationId, isReadOnly, isSnapshot);
		copyMemberValues(hndProperty, property, true);

		Parcel parcel;
		if (laSpatialUnitSet != null) {
			Set<SpatialUnit> spatialUnitSet = new HashSet<SpatialUnit>();
			for (LA_SpatialUnit laSpatialUnit : laSpatialUnitSet) {
				if (laSpatialUnit instanceof HND_Parcel) {
					parcel = HND_ParcelToParcel((HND_Parcel)laSpatialUnit, presentationId, isReadOnly, isSnapshot);
					
					//TODO: corregir esta situacion con los puntos de referencia!
					parcel.setReferencePoint(laSpatialUnit.getReferencePoint());
					
					LA_Point laPointOfReference = laSpatialUnit.getPointOfReference();
					if (laPointOfReference != null) {
						LAPoint pointOfReference = LA_PointToLAPoint(laPointOfReference, presentationId, isReadOnly, isSnapshot);
						pointOfReference.setSpatialUnit(parcel);
						
						parcel.setPointOfReference(pointOfReference);
					}
					SpatialUnitDAO.save(parcel);
					
					spatialUnitSet.add(parcel);
				}
			}
			
			property.setSpatialUnits(spatialUnitSet);
		}
		
		
		
		Set<LA_RRR> setLaRrr = hndProperty.getRrr();
		Set<RRR> setRrr = new HashSet<RRR>();
		if (setLaRrr != null && setLaRrr.size() > 0) {
			Map<Long,LA_Party> mapLaParty = new HashMap<Long,LA_Party>();
			for (LA_RRR laRrr : setLaRrr) {
				RRR rrr;
	
				rrr = LA_RRRToRRR(laRrr, presentationId, isReadOnly, isSnapshot);
				rrr.setBaunit(property);
				rrr.setPartyLadmId(laRrr.getParty().getpID());
				rrr.setType(laRrr.getType());
				rrr.setShare(laRrr.getShare());
				
				setRrr.add(rrr);
				mapLaParty.put(laRrr.getParty().getpID(), laRrr.getParty());
			}
			
			Map<Long,Party> mapParty = new HashMap<Long,Party>();
			Collection<LA_Party> collLAParty = mapLaParty.values();
			Party party;
			for (LA_Party laParty : collLAParty) {
				party = LA_PartyToParty(laParty, presentationId, isReadOnly, isSnapshot);
				party.setRrr(setRrr);
				mapParty.put(laParty.getpID(), party);
			}
			
			//TODO: Ver lo de los parties que se copien todos los miembros
			for (RRR rrr : setRrr) {
				party = mapParty.get(rrr.getPartyLadmId());
				rrr.setParty(party);
			}
	
			Collection<Party> collParty = mapParty.values();
			for (Party p : collParty)
				PartyDAO.save(p);
		}
		property.setRrr(setRrr);
		property.setOriginal(true);
		
		return property;
	}
	
	
	public static LAPoint LA_PointToLAPoint(LA_Point laPoint, long presentationId, boolean isReadOnly, boolean isSnapshot) {
		if (laPoint == null) return null;
		
		LAPoint point = LAPoint.newLAPoint(laPoint.getpID(), presentationId, isReadOnly, isSnapshot);
		copyMemberValues(laPoint, point, true);
		
		return point;
	}
	public static Parcel HND_ParcelToParcel(HND_Parcel hndParcel, long presentationId, boolean isReadOnly, boolean isSnapshot) {
		if (hndParcel == null) return null;
		
		Parcel parcel = Parcel.newParcel(hndParcel.getSuID(), presentationId, isReadOnly, isSnapshot);
		copyMemberValues(hndParcel, parcel, true);
		parcel.setOriginal(true);
		parcel.setShape(hndParcel.getShape());
		
		return parcel;
	}
	
	public static void applyRequestChanges(long presentationId, long baUnitId) {
		Property property = PropertyDAO.loadPropertyByPresentationIdAndLadmId(presentationId, baUnitId);
		Date todaysDate = new Date();
		
		//TODO: Ver primero si se crearon predios nuevos, por ahora no
		RRR rrr;
		RRRDAO rrrDao;
	}
	
	public static long copyParcelData(long presentationId, long parcelId, boolean includeNeighbors, boolean isSnapshot) {
		HND_Parcel hndParcel = HND_ParcelDAO.loadParcelById(parcelId);
		HND_Property hndProperty = hndParcel.getProperty();
		
		Set<LA_SpatialUnit> laSpatialUnitSet = new HashSet<LA_SpatialUnit>();
		laSpatialUnitSet.add(hndParcel);
		Property property = HND_PropertyToPropery(hndProperty, laSpatialUnitSet, presentationId, false, isSnapshot);
		
		//TODO: hacer lo de RRR en HND_PropertyToPropery
		
		PropertyDAO.save(property);

		if (includeNeighbors) {
			Set<HND_Parcel> hndNeighborParcelSet = HND_ParcelDAO.loadNeighborsByHNDParcel(hndParcel);
			if (hndNeighborParcelSet != null) {
				for (HND_Parcel hndNeighborParcel : hndNeighborParcelSet) {
					HND_Property hndNeighborProperty = hndNeighborParcel.getProperty();
					
					Property neighborProp = HND_PropertyToPropery(hndNeighborProperty, hndNeighborParcel, presentationId, true, isSnapshot);
					PropertyDAO.save(neighborProp);
				}
			}
		}
		
		return property.getORMID();
	}
	
	public static List<Long> copyParcelDataWithRelatedParcels(long presentationId, List<Long> parcelIdList, boolean includeNeighbors, boolean createSnapshot) {
		List<Long> baUnitIdList = new ArrayList<Long>();
		Set<HND_Property> hndRequestedPropertiesSet = new HashSet<HND_Property>();
		Set<HND_Property> hndPropertiesToSaveSet = new HashSet<HND_Property>();

		for (Long parcelId : parcelIdList) {
			HND_Parcel hndParcel = HND_ParcelDAO.loadParcelById(parcelId);
			
			HND_Property hndProperty = hndParcel.getProperty();
			hndRequestedPropertiesSet.add(hndProperty);
			hndPropertiesToSaveSet.add(hndProperty);
		}
		
		for (HND_Property hndProp : hndRequestedPropertiesSet) {
			Set<HND_Property> hndNeighborPropSet = HND_PropertyDAO.loadNeighborsByHNDProperty(hndProp);
			hndPropertiesToSaveSet.addAll(hndNeighborPropSet);
		}
		
		for (HND_Property hndProp : hndPropertiesToSaveSet) {
			Property prop;
			if (hndRequestedPropertiesSet.contains(hndProp)) {
				//If it is a requested property...
				prop = HND_PropertyToPropery(hndProp, presentationId, false, false);
				PropertyDAO.save(prop);
				baUnitIdList.add(prop.getORMID());
				if (createSnapshot) {
					prop = HND_PropertyToPropery(hndProp, presentationId, false, true);
					PropertyDAO.save(prop);
				}
			}
			else {
				//If it is just a neighbor property, it is readonly...
				prop = HND_PropertyToPropery(hndProp, presentationId, true, false);
				PropertyDAO.save(prop);
				if (createSnapshot) {
					prop = HND_PropertyToPropery(hndProp, presentationId, true, true);
					PropertyDAO.save(prop);
				}
			}
		}
		
		return baUnitIdList;
	}
	
	public static void replaceParcel(Parcel oldParcel, Parcel newParcel, Date endLifespanVersion, boolean replaceProperty) {
		oldParcel.setEndLifespanVersion(endLifespanVersion);
		oldParcel.setModified(true);
		newParcel.setBeginLifespanVersion(endLifespanVersion);
		newParcel.setSuID(0);
		
		Property theProperty = oldParcel.getProperty();
		
		theProperty.getSpatialUnits().remove(oldParcel);
		theProperty.getSpatialUnits().add(newParcel);
		
		if (replaceProperty) {
			Property theNewProperty = theProperty.clone();
			
			theProperty.setEndLifespanVersion(endLifespanVersion);
			
			theNewProperty.setuID(0);
			theNewProperty.setBeginLifespanVersion(endLifespanVersion);
			theNewProperty.getSpatialUnits().clear();
			theNewProperty.getSpatialUnits().add(newParcel);
			
			ParcelDAO.save(oldParcel);
			ParcelDAO.save(newParcel);
			PropertyDAO.save(theProperty);
			PropertyDAO.save(theNewProperty);
		}
		else {
			ParcelDAO.save(oldParcel);
			ParcelDAO.save(newParcel);
			PropertyDAO.save(theProperty);
		}
	}
	
	public static void undoLastOperation(long presentationId) {
		//TODO: Por ahora, solo hace UNDO de parcelas
		//agregar soporte para propiedades y derechos
		Date parcelTS = ParcelDAO.loadMaxTimeStamp(presentationId);
		System.out.println(parcelTS);
	}
	
	public static void deletePresentationShadow(long presentationId, boolean includeSnapshot) {
		//delete associations
		RRRDAO.clearRRRsAssociationsByPresentationId(presentationId, includeSnapshot);
		PropertyDAO.clearPropertiesAssociationsByPresentationId(presentationId, includeSnapshot);
		ParcelDAO.clearParcelsAssociationsByPresentationId(presentationId, includeSnapshot);
		
		//delete entities
		RRRDAO.deleteRRRsByPresentationId(presentationId, includeSnapshot);
		PropertyDAO.deletePropertiesByPresentationId(presentationId, includeSnapshot);
		ParcelDAO.deleteParcelsByPresentationId(presentationId, includeSnapshot);
	}
	/*
	private static void copySnapshot(long presentationId) {
		
		List<RRR> rrrList = RRRDAO.loadRRRsByPresentationId(presentationId, true);
		for (RRR rrr : rrrList) {
			RRR clonedRrr = rrr
		}
	}
	*/
	public static void undoAll(long presentationId, List<Long> parcelIdList) {
		//TODO: validar que si no se ha hecho ningun cambio entonces no ejecutar nada!!
		RRRDAO.clear();
		deletePresentationShadow(presentationId, true);
		
		Util.copyParcelDataWithRelatedParcels( presentationId, parcelIdList, true, true );
	}
}
